﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Xml.Linq;
using agsXMPP;
using agsXMPP.protocol.client;
using Lapd.Common.CustomStanzas;
using Lapd.Common.Entities;

namespace Lapd.Common.Helpers
{
    public static class ServiceDdsHelper
    {
        #region Member

        /// <summary>
        /// There is no way to obtain the alias type of a variable (e.g. "int"). This dictionary
        /// help us to do that, obtaining an alias type from its qualified name.
        /// We need the alias type to send to the topic
        /// </summary>
        private static Dictionary<string, string> _typeAliases = new Dictionary<string, string>
                                                                     {
                                                                            {"System.Boolean", "bool"   },
                                                                            {"System.Byte"   , "byte"   },
                                                                            {"System.SByte"  , "sbyte"  },
                                                                            {"System.Char"   , "char"   },
                                                                            {"System.Decimal", "decimal"},
                                                                            {"System.Double" , "double" },
                                                                            {"System.Single" , "float"  },
                                                                            {"System.Int32"  , "int"    },
                                                                            {"System.UInt32" , "uint"   },
                                                                            {"System.Int64"  , "long"   },
                                                                            {"System.UInt64" , "ulong"  },
                                                                            {"System.Object" , "object" },
                                                                            {"System.Int16"  , "short"  },
                                                                            {"System.UInt16" , "ushort" },
                                                                            {"System.String" , "string" }
                                                                         };

        #endregion

        #region Service DDS operations

        public static void Login(string server, string userName, string domain, string password, ref XmppClientConnection xmppClientConnection)
        {
            Jid jid = new Jid(userName + "@" + domain);

            xmppClientConnection.Server = jid.Server;
            xmppClientConnection.Username = userName;
            xmppClientConnection.Password = password;
            xmppClientConnection.AutoResolveConnectServer = false;
            xmppClientConnection.ConnectServer = server;
            xmppClientConnection.UseStartTLS = false;

            xmppClientConnection.Open();
        }

        public static IQ JointToGroup(string groupName, ref XmppClientConnection xmppClientConnection)
        {
            // Prepare SddsIq JOIN packet
            Jid jidTo = new Jid(ServiceDdsConstants.JID_TO_PREFFIX + "." + Session.Domain);
            Join join = new Join(groupName);
            Sdds sdds = new Sdds(join);
            SddsIq sddsIq = new SddsIq(sdds) { Type = IqType.set, To = jidTo };

            // Sync call
            IQ iq = xmppClientConnection.IqGrabber.SendIq(sddsIq);

            return iq;
        }

        public static IQ CreateTopic(string dataType, string instanceName, ref XmppClientConnection xmppClientConnection)
        {
            // Prepare SddsIq NEW packet
            Jid jidTo = new Jid(ServiceDdsConstants.JID_TO_PREFFIX + "." + Session.Domain);

            New newPacket = new New(dataType, instanceName);

            Sdds sdds = new Sdds(newPacket);
            SddsIq sddsIq = new SddsIq(sdds)
            {
                Type = IqType.set,
                To = jidTo,
            };

            // Sync call
            IQ iq = xmppClientConnection.IqGrabber.SendIq(sddsIq);
            return iq;
        }

        public static IQ WriteOnTopic(string topic, ref XmppClientConnection xmppClientConnection, params object[] paramList)
        {
            // Prepare SddsIq WRITE packet
            Jid jidTo = new Jid(ServiceDdsConstants.JID_TO_PREFFIX + "." + Session.Domain);

            Write write = new Write(topic);

            foreach (object param in paramList)
            {
                // For number types we need to use invariant culture on ToString() method.
                // If we don't use it, Windows Mobile will use its local culture (if it is spanish,
                // it will send 45,67 for example, and the parse on ServiceDDS side will fail)

                // Exception example:
                // ------------------
                // ServiceDDSOpenfire.processPacket() detected 'write' command...
                // Could not route packet
                // java.lang.NumberFormatException: For input string: "47,6336266666667"

                // double
                if (param.GetType() == typeof(double))
                {
                    double paramDouble = (double)param;
                    write.AddSampleField(new SampleField(_typeAliases[param.GetType().ToString()], paramDouble.ToString(CultureInfo.InvariantCulture)));
                }
                // Add here types on demmand (commented example with "int")
                //else if (param.GetType() == typeof(int))
                //{
                //    int paramInt = (int)param;
                //    write.AddSampleField(new SampleField(_typeAliases[param.GetType().ToString()], paramInt.ToString(CultureInfo.InvariantCulture)));
                //}
                // ...
                // Rest of types
                else
                {
                    write.AddSampleField(new SampleField(_typeAliases[param.GetType().ToString()], param.ToString()));
                }
            }

            Sdds sdds = new Sdds(write);
            SddsIq sddsIq = new SddsIq(sdds) { Type = IqType.set, To = jidTo };

            // Sync call
            IQ iq = xmppClientConnection.IqGrabber.SendIq(sddsIq);
            return iq;
        }

        public static IQ ListenFromTopic(string topicName, ref XmppClientConnection xmppClientConnection)
        {
            // Prepare SddsIq LISTEN packet
            Jid jidTo = new Jid(ServiceDdsConstants.JID_TO_PREFFIX + "." + Session.Domain);

            Listen listen = new Listen(topicName);

            Sdds sdds = new Sdds(listen);
            SddsIq sddsIq = new SddsIq(sdds) {Type = IqType.set, To = jidTo};

            // Sync call
            IQ iq = xmppClientConnection.IqGrabber.SendIq(sddsIq);
            return iq;
        }

        public static void InitializeServiceDDS(ref XmppClientConnection xmppClientConnection)
        {
            // 2.- Join to group (sync call)
            IQ iq = JointToGroup(Session.GroupName, ref xmppClientConnection);
            if (iq == null)
            {
                throw new Exception(String.Format("Se ha producido un error al intentar unirse al grupo '{0}'", Session.GroupName));

            }

            // 3.1- Create alarm topic (sync call)
            iq = CreateTopic(
                ServiceDdsConstants.ALARM_TOPIC_DATA_TYPE,
                ServiceDdsConstants.ALARM_TOPIC_INSTANCE_NAME,
                ref xmppClientConnection);

            if (iq == null)
            {
                throw new Exception(String.Format("Se ha producido un error al intentar crear el tópico '{0}'", ServiceDdsConstants.ALARM_TOPIC_INSTANCE_NAME));
            }

            // 3.2- Create warning request topic (sync call)
            iq = CreateTopic(
                ServiceDdsConstants.WARNING_REQUEST_TOPIC_DATA_TYPE,
                ServiceDdsConstants.WARNING_REQUEST_TOPIC_INSTANCE_NAME,
                ref xmppClientConnection);

            if (iq == null)
            {
                throw new Exception(String.Format("Se ha producido un error al intentar crear el tópico '{0}'", ServiceDdsConstants.WARNING_REQUEST_TOPIC_INSTANCE_NAME));
            }

            // 3.3- Create warning response topic (sync call)
            iq = CreateTopic(
                ServiceDdsConstants.WARNING_RESPONSE_TOPIC_DATA_TYPE,
                ServiceDdsConstants.WARNING_RESPONSE_TOPIC_INSTANCE_NAME,
                ref xmppClientConnection);

            if (iq == null)
            {
                throw new Exception(String.Format("Se ha producido un error al intentar crear el tópico '{0}'", ServiceDdsConstants.WARNING_RESPONSE_TOPIC_INSTANCE_NAME));
            }

            // 3.4- Create location alarm topic (sync call)
            iq = CreateTopic(
                ServiceDdsConstants.LOCATION_ALARM_TOPIC_DATA_TYPE,
                ServiceDdsConstants.LOCATION_ALARM_TOPIC_INSTANCE_NAME,
                ref xmppClientConnection);

            if (iq == null)
            {
                throw new Exception(String.Format("Se ha producido un error al intentar crear el tópico '{0}'", ServiceDdsConstants.LOCATION_ALARM_TOPIC_INSTANCE_NAME));
            }

            // 3.5- Create location request topic (sync call)
            iq = CreateTopic(
                ServiceDdsConstants.LOCATION_REQUEST_TOPIC_DATA_TYPE,
                ServiceDdsConstants.LOCATION_REQUEST_TOPIC_INSTANCE_NAME,
                ref xmppClientConnection);

            if (iq == null)
            {
                throw new Exception(String.Format("Se ha producido un error al intentar crear el tópico '{0}'", ServiceDdsConstants.LOCATION_REQUEST_TOPIC_INSTANCE_NAME));
            }

            // 3.6- Create location response topic (sync call)
            iq = CreateTopic(
                ServiceDdsConstants.LOCATION_RESPONSE_TOPIC_DATA_TYPE,
                ServiceDdsConstants.LOCATION_RESPONSE_TOPIC_INSTANCE_NAME,
                ref xmppClientConnection);

            if (iq == null)
            {
                throw new Exception(String.Format("Se ha producido un error al intentar crear el tópico '{0}'", ServiceDdsConstants.LOCATION_RESPONSE_TOPIC_INSTANCE_NAME));
            }

            // 4.1- Listen from alarm topic (sync call)
            iq = ListenFromTopic(ServiceDdsConstants.ALARM_TOPIC_INSTANCE_NAME, ref xmppClientConnection);
            if (iq == null)
            {
                throw new Exception(String.Format("Se ha producido un error al intentar escuchar del tópico '{0}'", ServiceDdsConstants.ALARM_TOPIC_INSTANCE_NAME));
            }

            // 4.2- Listen from warning request topic (sync call)
            iq = ListenFromTopic(ServiceDdsConstants.WARNING_REQUEST_TOPIC_INSTANCE_NAME, ref xmppClientConnection);
            if (iq == null)
            {
                throw new Exception(String.Format("Se ha producido un error al intentar escuchar del tópico '{0}'", ServiceDdsConstants.WARNING_REQUEST_TOPIC_INSTANCE_NAME));
            }

            // 4.3- Listen from warning request topic (sync call)
            iq = ListenFromTopic(ServiceDdsConstants.WARNING_RESPONSE_TOPIC_INSTANCE_NAME, ref xmppClientConnection);
            if (iq == null)
            {
                throw new Exception(String.Format("Se ha producido un error al intentar escuchar del tópico '{0}'", ServiceDdsConstants.WARNING_RESPONSE_TOPIC_INSTANCE_NAME));
            }

            // 4.4- Listen from location alarm topic (sync call)
            iq = ListenFromTopic(ServiceDdsConstants.LOCATION_ALARM_TOPIC_INSTANCE_NAME, ref xmppClientConnection);
            if (iq == null)
            {
                throw new Exception(String.Format("Se ha producido un error al intentar escuchar del tópico '{0}'", ServiceDdsConstants.LOCATION_ALARM_TOPIC_INSTANCE_NAME));
            }

            // 4.5- Listen from location request topic (sync call)
            iq = ListenFromTopic(ServiceDdsConstants.LOCATION_REQUEST_TOPIC_INSTANCE_NAME, ref xmppClientConnection);
            if (iq == null)
            {
                throw new Exception(String.Format("Se ha producido un error al intentar escuchar del tópico '{0}'", ServiceDdsConstants.LOCATION_REQUEST_TOPIC_INSTANCE_NAME));
            }

            // 4.6- Listen from location response topic (sync call)
            iq = ListenFromTopic(ServiceDdsConstants.LOCATION_RESPONSE_TOPIC_INSTANCE_NAME, ref xmppClientConnection);
            if (iq == null)
            {
                throw new Exception(String.Format("Se ha producido un error al intentar escuchar del tópico '{0}'", ServiceDdsConstants.LOCATION_RESPONSE_TOPIC_INSTANCE_NAME));
            }
   
        }

        #endregion

        #region XML helper operations

        /*
         * AlarmTopic sample
         * ---------------------
        <message to="personadependiente1@portatil-mikelo/agsXMPP" from="portatil-mikelo">
            <subject>AlarmTopic.on_data_available</subject>
            <body>on_data_available</body>
            <sdds xmlns="sddsopenfire" command="on_data_available">
                <sample instancename="AlarmTopic">
                    <samplefield dataype="long" value="68"/>
                    <samplefield dataype="string" value="Help"/>
                </sample>
            </sdds>
        </message>
        */
        public static string GetInstanceName(Message msg)
        {
            string instanceName = null;

            // Parse XML string
            XElement rootElement = XElement.Parse(msg.ToString());

            // Get <sample> element
            XNamespace sddsOpenfireNamespace = ServiceDdsConstants.SDDS_OPENFIRE_NAMESPACE;
            XElement sampleXElement = (from sample in rootElement.Descendants(sddsOpenfireNamespace
                                                                                 + ServiceDdsConstants.WRITE_SAMPLE_TAG)
                                       select sample).SingleOrDefault();

            if (sampleXElement != null)
            {
                // Get instance name attribute
                XAttribute instanceNameAttribute = sampleXElement.Attribute(ServiceDdsConstants.WRITE_INSTANCE_ATTRIBUTE);

                if (instanceNameAttribute != null)
                {
                    instanceName = instanceNameAttribute.Value;
                }
            }

            return instanceName;

        }

        public static IEnumerable<XElement> GetSampleFieldXElements(Message msg)
        {
            // Parse XML string
            XElement rootElement = XElement.Parse(msg.ToString());

            // Search <samplefield> elements
            XNamespace sddsOpenfireNamespace = ServiceDdsConstants.SDDS_OPENFIRE_NAMESPACE;
            IEnumerable<XElement> sampleFieldXElements = from sampleField in rootElement.Descendants(sddsOpenfireNamespace
                                                                                 + ServiceDdsConstants.WRITE_SAMPLE_FIELD_TAG)
                                                    select sampleField;

            return sampleFieldXElements;
        }

        /// <summary>
        /// Convert XML samplefield elements into samplefield entities
        /// </summary>
        /// <param name="sampleFieldXElements"></param>
        /// <returns></returns>
        public static IEnumerable<SampleFieldEntity> SampleFieldXElementsToSampleFieldEntities(IEnumerable<XElement> sampleFieldXElements)
        {
            List<SampleFieldEntity> sampleFieldEntities = new List<SampleFieldEntity>();
            SampleFieldEntity sampleFieldEntity;

            foreach (XElement sampleFieldXElement in sampleFieldXElements)
            {
                sampleFieldEntity = new SampleFieldEntity();

                // Datatype
                XAttribute dataTypeXAttribute = sampleFieldXElement.Attributes(ServiceDdsConstants.WRITE_SAMPLE_FIELD_DATATYPE_ATTRIBUTE_MISPRINT).First();
                sampleFieldEntity.DataType = dataTypeXAttribute.Value;

                // Value
                XAttribute valueXAttribute = sampleFieldXElement.Attributes(ServiceDdsConstants.WRITE_SAMPLE_FIELD_VALUE_ATTRIBUTE).First();
                sampleFieldEntity.Value = valueXAttribute.Value;

                sampleFieldEntities.Add(sampleFieldEntity);
            }

            return sampleFieldEntities;
        }

        // TODO: Make a generic method like this: GetSampleFieldAt(Message msg, int position)
        public static string GetWarningResponseValue(Message msg)
        {
            string responseValue = null;

            IEnumerable<XElement> sampleFieldXElements = GetSampleFieldXElements(msg);
            IEnumerable<SampleFieldEntity> sampleFieldEntities = SampleFieldXElementsToSampleFieldEntities(sampleFieldXElements);

            if (sampleFieldEntities != null && sampleFieldEntities.Count() == 2)
            {
                // Value on position 1
                responseValue = sampleFieldEntities.ElementAt(1).Value;
            }

            return responseValue;
        }

        public static void GetLatitudeAndLongitude(Message msg, out double latitude, out double longitude)
        {
            latitude = 0;
            longitude = 0;
            
            IEnumerable<XElement> sampleFieldXElements = GetSampleFieldXElements(msg);
            IEnumerable<SampleFieldEntity> sampleFieldEntities = SampleFieldXElementsToSampleFieldEntities(sampleFieldXElements);

            if (sampleFieldEntities != null && sampleFieldEntities.Count() == 3)
            {
                // Value on position 1
                latitude = double.Parse(sampleFieldEntities.ElementAt(1).Value, CultureInfo.InvariantCulture);
                longitude = double.Parse(sampleFieldEntities.ElementAt(2).Value, CultureInfo.InvariantCulture);
            }
        }

        #endregion

    }

}
